package wsi.dataguide;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.rygh.core.util.UTime;
import org.rygh.core.util.counter.SimpleCounter;
import org.rygh.core.xml.XmlBuilder;
import org.rygh.core.xml.XmlPath;

import wsi.dataguide.tree.WSXmlLeaf;
import wsi.dataguide.tree.WSXmlObject;
import wsi.dataguide.tree.WSXmlTree;
import wsi.dataguide.tree.XmlObjectOccurrence;

public class DataGuide extends WSXmlTree {

	private final Map<WSXmlLeaf, WSXmlLeaf> xmlNodeMap = new HashMap<>();
	private final SimpleCounter<WSXmlLeaf> occurCounter = new SimpleCounter<>();
	private final List<WSXmlLeaf> leaves = new ArrayList<>();

	private int treeCount = 1;

	public DataGuide() {
		super(new WSXmlObject("dataguide"));

		addPrefix("uvsq", "http://prism.uvsq.fr/dataguide");
	}

	public void clear() {
		xmlNodeMap.clear();
		occurCounter.clear();
	}

	public void count(WSXmlLeaf node, WSXmlLeaf nodeInXml) {
		if (occurCounter.contains(node) && xmlNodeMap.containsKey(node)) {
			if (xmlNodeMap.get(node).getParent() == nodeInXml.getParent())
				occurCounter.count(node);
		} else {
			occurCounter.count(node);
			xmlNodeMap.put(node, nodeInXml);
		}
	}

	/**
	 * get the structurally isomorphic leaf in data guide w.r.t. a leaf in a
	 * subtree in XML document. this helps generate triples.
	 * 
	 * @param leaf
	 * @return
	 */
	public WSXmlLeaf getIsomorphicLeaf(WSXmlLeaf leaf) {
		if (leaves.isEmpty())
			leaves.addAll(getAllLeaves());

		XmlPath xpath = leaf.getXpath();

		for (WSXmlLeaf i : leaves)
			if (i.getXpath().subsumesWithoutIndex(xpath))
				return i;

		// check if there is a corresponding self child

		XmlPath xpath2 = new XmlPath(xpath.getValue() + "/" + XmlPath.SELF_TAG);

		for (WSXmlLeaf i : leaves)
			if (i.getXpath().subsumesWithoutIndex(xpath2))
				return i;

		// this means the leaf passed in here is not a leaf in dataguide. then
		// not qualified to be a property.
		return null;
	}

	/**
	 * non attribute children
	 * 
	 * @return
	 */
	public WSXmlLeaf getNodeWithMaxChildren() {
		List<WSXmlLeaf> all_nodes = getAllNodes();

		WSXmlLeaf node = null;
		int size = -1, c;

		for (WSXmlLeaf n : all_nodes) {
			if (n.isLeaf() || n.isRoot())
				continue;

			c = 0;

			for (WSXmlLeaf child : n.getChildren())
				if (!child.getValue().isAttribute())
					c++;

			if (size < c) {
				size = n.getChildren().size();
				node = n;
			}
		}

		if (size == 1)
			node = getConcreteRoot();

		return node;
	}

	public List<WSXmlLeaf> getNodeWithMoreOccurrence() {
		List<WSXmlLeaf> nodes = new ArrayList<>();

		List<WSXmlLeaf> all_nodes = getAllNodes();

		for (WSXmlLeaf n : all_nodes)
			if (n.getValue().getOccurence().getMax() > 1)
				nodes.add(n);

		Collections.sort(nodes, new Comparator<WSXmlLeaf>() {
			@Override
			public int compare(WSXmlLeaf o1, WSXmlLeaf o2) {
				/*
				 * first by level, lower -> better;
				 * 
				 * second by occur, larger -> better.
				 * 
				 * 130924: next time, record the example where level be
				 * respected.
				 * 
				 * .../artist-list/artist[25]
				 * 
				 * .../artist-list/artist/tag-list/tag[25]
				 */

				// if (o2.getLevel() != o1.getLevel())
				// return o1.getLevel() - o2.getLevel();
				// else
				return o2.getValue().getOccurence().getMax()
						- o1.getValue().getOccurence().getMax();
			}
		});

		return nodes;
	}

	public int getOccurence(WSXmlLeaf node) {
		return occurCounter.get(node);
	}

	public WSXmlLeaf getSelectedNode() {
		List<WSXmlLeaf> all_nodes = getAllNodes();

		for (WSXmlLeaf n : all_nodes)
			if (n.getValue().isSelected())
				return n;

		return null;
	}

	/**
	 * there are valid nodes between root (metadata) and list root node
	 * (artist-list)
	 * 
	 * for example, there are nodes about an artist above release-list
	 * 
	 * @param node
	 *            a node of data guide.
	 * @return
	 */
	public boolean hasSupNodes(WSXmlLeaf node) {
		WSXmlLeaf parent = node.getParent();

		List<WSXmlLeaf> nodes = getNodesAbove(parent);

		for (WSXmlLeaf n : nodes) {
			if (n.isRoot() || n == getConcreteRoot()
					|| n == super.getConcreteRoot())
				continue;

			return true;
		}

		return false;
	}

	public String toString(int valueLimit) {
		XmlBuilder sbuilder = new XmlBuilder();

		toString(sbuilder, root, valueLimit);

		return sbuilder.getValue();
	}

	private void toString(XmlBuilder sbuilder, WSXmlLeaf leaf, int valueLimit) {
		WSXmlObject node = leaf.getValue();

		if (leaf.isRoot())
			sbuilder.append(String.format("<%s %s uvsq:created=\"%s\">",
					node.getName(), getPrefixString(), UTime.toTodayString()));
		else
			sbuilder.append(node.toXmlTagString());

		if (leaf.isLeaf()) {
			sbuilder.append(node.toDataguideString(valueLimit));
		} else {
			for (WSXmlLeaf i : leaf.getChildren())
				toString(sbuilder, i, valueLimit);
		}

		sbuilder.end(node.getName());
	}

	public void updateOccurrence() {
		updateOccurrence(root);
		treeCount++;
	}

	private void updateOccurrence(WSXmlLeaf node) {
		if (node.getValue() != null && occurCounter.contains(node)) {
			int c = getOccurence(node);
			XmlObjectOccurrence occur = node.getValue().getOccurence();

			occur.update(c);
			occur.calculateAverage(treeCount);
		}

		if (!node.isLeaf())
			for (WSXmlLeaf child : node.getChildren())
				updateOccurrence(child);
	}

}
